import { DeviceInputKey, IInputDevice, InputStatus } from './device-information';
import { Scene } from "@babylonjs/core/scene";
import { DeviceSourceManager } from "@babylonjs/core/DeviceInput/InputDevices/deviceSourceManager";
import { DeviceSourceType } from "@babylonjs/core/DeviceInput/internalDeviceSourceManager";
import { DeviceType } from "@babylonjs/core/DeviceInput/InputDevices/deviceEnums";
import { AnimationFrame, ITimer } from '../../animation/animation-frame';
import { InputKeyStatus } from './input-key-status';
import { XboxPreset } from './device-preset/xbox-preset';
import { DeviceSource } from '@babylonjs/core/DeviceInput/InputDevices/deviceSource';
import { SwitchPreset } from './device-preset/switch-preset';
import { DualSensePreset } from './device-preset/dual-sense-preset';
import { DualShockPreset } from './device-preset/dual-shock-preset';
import { KeyboardPreset } from './device-preset/keyboard-preset';
import { MousePreset } from './device-preset/mouse-preset';
import { TouchPreset } from './device-preset/touch-preset';
import { GenericPreset } from './device-preset/generic-preset';
import { InputPointerStatus } from './input-pointer-status';
import { PointerInfo } from '@babylonjs/core/Events/pointerEvents';


/**
 * 设备输入预设参数对象
 * @param checkInterval 检测输入的时间间隔（默认50毫秒）
 */
interface IDeviceInputPresetOptions {
    checkInterval?: number,
}

/**
 * 设备输入回调函数对象
 * @param id 回调函数对象id
 * @param keys 响应的输入键值集合
 * @param handleIds 分配到输入键对象的回调函数对象id
 * @param status 响应的输入键状态
 * @param callback 要执行的回调函数
 */
interface IDeviceInputHandle {
    id: number,
    keys: DeviceInputKey[],
    handleIds: Array<number>,
    status: InputStatus,
    callback: { (e?: PointerInfo): void }
}

let _handleId = 0;


/**
 * 设备输入预设（支持键盘、鼠标、触摸、xbox手柄、switch手柄、ps4手柄、ps5手柄、通用手柄（待扩充）），
 * 使用前应先使用initialize函数初始化
 */
class DeviceInputPreset {

    private static _scene: Scene;
    private static _deviceMgr: DeviceSourceManager;
    private static _device: IInputDevice = {};
    private static _options: IDeviceInputPresetOptions;
    private static _req: ITimer = {};
    private static _keyPool: Array<InputKeyStatus<DeviceType> | InputPointerStatus> = [];
    private static _activeKeyPool: Array<InputKeyStatus<DeviceType> | InputPointerStatus> = [];
    private static _inputHandles: IDeviceInputHandle[] = [];
    private static _initialized: boolean = false;

    public static get manager(): DeviceSourceManager {
        return this._deviceMgr;
    }

    public static get device(): IInputDevice {
        return this._device;
    }

    public static get pool(): Array<InputKeyStatus<DeviceType> | InputPointerStatus> {
        return this._keyPool;
    }

    public static get activeKeys(): Array<InputKeyStatus<DeviceType> | InputPointerStatus> {
        return this._activeKeyPool;
    }

    public static get initialized(): boolean {
        return this._initialized;
    }

    /**
     * 初始化组件
     * @param scene 场景
     * @param options 参数对象
     */
    public static initialize(scene: Scene, options?: IDeviceInputPresetOptions): void {
        this._scene = scene;
        const ops = options || {};
        ops.checkInterval = ops.checkInterval || 50;
        this._options = ops;

        this._keyPool.push(new InputPointerStatus(DeviceInputKey.pointer, this._scene));
        this._deviceMgr = new DeviceSourceManager(this._scene.getEngine());
        this._deviceMgr.onDeviceConnectedObservable.add(this.connectedHandle);
        this._deviceMgr.onDeviceDisconnectedObservable.add(this.disconnectedHandle);
        this._initialized = true;
    }

    /**
     * 根据id获取设备输入回调函数对象
     * @param id 设备输入回调函数对象的id
     * @returns 设备输入回调函数对象
     */
    public static getInputHandleById(id: number): IDeviceInputHandle | null {
        for (const handle of zs._inputHandles) {
            if (handle.id === id) return handle;
        }
        return null;
    }

    /**
     * 获取包含给定输入键集合中任意输入键的设备输入回调函数对象
     * @param out 存放设备输入回调函数对象的集合
     * @param keys 给定输入键集合
     * @returns 设备输入回调函数对象集合
     */
    public static getInputHandleByAnyKey(out: IDeviceInputHandle[], ...keys: DeviceInputKey[]): IDeviceInputHandle[] {
        for (const handle of zs._inputHandles) {
            for (const key of keys) {
                if (handle.keys.includes(key)) {
                    out.push(handle);
                    break;
                }
            }
        }
        return out;
    }

    /**
     * 添加设备输入回调函数对象
     * @param callback 回调函数
     * @param status 输入键的响应状态
     * @param keys 输入键集合
     * @returns 设备输入回调函数对象的id
     */
    public static onInput(callback: { (e?: PointerInfo): void }, status: InputStatus, ...keys: DeviceInputKey[]): number {
        _handleId++;
        const handle: IDeviceInputHandle = {
            id: _handleId,
            keys: keys,
            handleIds: [],
            status: status,
            callback: callback,
        };
        zs._inputHandles.push(handle);
        zs.processInputHandle(handle);
        return _handleId;
    }

    /**
     * 移除设备输入回调函数对象
     * @param handleId 设备输入回调函数对象的id
     */
    public static removeInputHandle(handleId: number): void {
        for (let i = 0; i < zs._inputHandles.length; i++) {
            const handle = zs._inputHandles[i];
            if (handle.id !== handleId) continue;
            for (const key of handle.keys) {
                for (let j = 0; j < zs._activeKeyPool.length; j++) {
                    const iks = zs._activeKeyPool[j];
                    if (iks.key !== key) continue;
                    const hds = handle.handleIds;
                    switch (handle.status) {
                        case InputStatus.click:
                            hds.forEach(hd => iks.removeClickHandle(hd));
                            break;
                        case InputStatus.press:
                            hds.forEach(hd => iks.removePressHandle(hd));
                            break;
                        case InputStatus.down:
                            hds.forEach(hd => iks.removeDownHandle(hd));
                            break;
                        case InputStatus.up:
                            hds.forEach(hd => iks.removeUpHandle(hd));
                            break;
                        case InputStatus.move:
                            if (iks instanceof InputPointerStatus) {
                                hds.forEach(hd => iks.removeMoveHandle(hd));
                            }
                            break;
                        case InputStatus.wheel:
                            if (iks instanceof InputPointerStatus) {
                                hds.forEach(hd => iks.removeWheelHandle(hd));
                            }
                            break;
                    }
                    if (iks.isIdle) {
                        zs._activeKeyPool.splice(j, 1);
                    }
                }
            }
            zs._inputHandles.splice(i, 1);
            return;
        }
    }

    /**
     * 创建输入键的回调函数对象
     * @param handle 设备输入回调函数对象
     */
    private static processInputHandle(handle: IDeviceInputHandle): void {
        for (const key of handle.keys) {
            for (const iks of zs._keyPool) {
                if (iks.key !== key) continue;
                switch (handle.status) {
                    case InputStatus.click:
                        if (iks.isIdle) zs._activeKeyPool.push(iks);
                        handle.handleIds.push((iks as InputPointerStatus).onClick((e) => { handle.callback(e) }).id);
                        break;
                    case InputStatus.press:
                        if (iks.isIdle) zs._activeKeyPool.push(iks);
                        handle.handleIds.push((iks as InputPointerStatus).onPress((e) => { handle.callback(e) }).id);
                        break;
                    case InputStatus.down:
                        if (iks.isIdle) zs._activeKeyPool.push(iks);
                        handle.handleIds.push((iks as InputPointerStatus).onDown((e) => { handle.callback(e) }).id);
                        break;
                    case InputStatus.up:
                        if (iks.isIdle) zs._activeKeyPool.push(iks);
                        handle.handleIds.push((iks as InputPointerStatus).onUp((e) => { handle.callback(e) }).id);
                        break;
                    case InputStatus.move:
                        if (iks.isIdle) zs._activeKeyPool.push(iks);
                        handle.handleIds.push((iks as InputPointerStatus).onMove((e) => { handle.callback(e) }).id);
                        break;
                    case InputStatus.wheel:
                        if (iks.isIdle) zs._activeKeyPool.push(iks);
                        handle.handleIds.push((iks as InputPointerStatus).onWheel((e) => { handle.callback(e) }).id);
                        break;
                }
            }
        }
    }

    /**
     * 新设备链接时，追加已存在的输入回调函数
     * @param ikss 新设备的输入键对象集合
     */
    private static appendHandle(...ikss: InputKeyStatus<DeviceType>[]): void {
        for (const iks of ikss) {
            for (const handle of zs._inputHandles) {
                if (handle.keys.includes(iks.key)) {
                    switch (handle.status) {
                        case InputStatus.click:
                            handle.handleIds.push(iks.onClick(() => { handle.callback() }).id);
                            break;
                        case InputStatus.press:
                            handle.handleIds.push(iks.onPress(() => { handle.callback() }).id);
                            break;
                        case InputStatus.down:
                            handle.handleIds.push(iks.onDown(() => { handle.callback() }).id);
                            break;
                        case InputStatus.up:
                            handle.handleIds.push(iks.onUp(() => { handle.callback() }).id);
                            break;
                    }
                }
            }
            if (!iks.isIdle) zs._activeKeyPool.push(iks);
        }
    }

    /**
     * 新设备连接时的处理函数
     * @param e 新设备源类型
     */
    private static connectedHandle(e: DeviceSourceType): void {
        let preset: any = null;
        switch (e.deviceType) {
            case DeviceType.Keyboard:
                if (zs._device.keyboard !== undefined) return;
                zs._device.keyboard = zs._deviceMgr.getDeviceSource(DeviceType.Keyboard)!;
                preset = KeyboardPreset(zs._device.keyboard);
                break;
            case DeviceType.Mouse:
                if (zs._device.mouse !== undefined) return;
                zs._device.mouse = zs._deviceMgr.getDeviceSource(DeviceType.Mouse)!;
                preset = MousePreset(zs._device.mouse);
                break;
            case DeviceType.Touch:
                if (zs._device.touch !== undefined) return;
                zs._device.touch = zs._deviceMgr.getDeviceSource(DeviceType.Touch)!;
                preset = TouchPreset(zs._device.touch);
                break;
            case DeviceType.Xbox:
                if (zs._device.xbox !== undefined) return;
                zs._device.xbox = zs._deviceMgr.getDeviceSource(DeviceType.Xbox)!;
                preset = XboxPreset(zs._device.xbox);
                break;
            case DeviceType.Switch:
                if (zs._device.switch !== undefined) return;
                zs._device.switch = zs._deviceMgr.getDeviceSource(DeviceType.Switch)!;
                preset = SwitchPreset(zs._device.switch);
                break;
            case DeviceType.DualSense:
                if (zs._device.dualScene !== undefined) return;
                zs._device.dualScene = zs._deviceMgr.getDeviceSource(DeviceType.DualSense)!;
                preset = DualSensePreset(zs._device.dualScene);
                break;
            case DeviceType.DualShock:
                if (zs._device.dualShock !== undefined) return;
                zs._device.dualShock = zs._deviceMgr.getDeviceSource(DeviceType.DualShock)!;
                preset = DualShockPreset(zs._device.dualShock);
                break;
            case DeviceType.Generic:
                if (zs._device.generic !== undefined) return;
                zs._device.generic = zs._deviceMgr.getDeviceSource(DeviceType.Generic)!;
                preset = GenericPreset(zs._device.generic);
                break;
            default:
                console.warn('unknown type of input device!');
        }
        if (!preset) return;
        zs._keyPool.push(...Object.values(preset) as any);
        zs.appendHandle(...Object.values(preset) as any);
        zs.startCheck();
    }

    /**
     * 设备连接断开时移除输入键对象的相关回调函数
     * @param type 设备类型
     */
    private static subtractHandle(type: DeviceType): void {
        for (let i = 0; i < zs._keyPool.length; i++) {
            const iks = zs._keyPool[i];
            if (iks.key === DeviceInputKey.pointer) continue
            if ((iks as InputKeyStatus<DeviceType>).device.deviceType !== type) continue;
            const handles = zs.getInputHandleByAnyKey([], iks.key);
            for (const handle of handles) {
                switch (handle.status) {
                    case InputStatus.click:
                        handle.handleIds.forEach(hid => iks.removeClickHandle(hid));
                        break;
                    case InputStatus.press:
                        handle.handleIds.forEach(hid => iks.removePressHandle(hid));
                        break;
                    case InputStatus.down:
                        handle.handleIds.forEach(hid => iks.removeDownHandle(hid));
                        break;
                    case InputStatus.up:
                        handle.handleIds.forEach(hid => iks.removeUpHandle(hid));
                        break;
                }
            }
            if (iks.isIdle) {
                for (let j = 0; j < zs._activeKeyPool.length; j++) {
                    const ika = zs._activeKeyPool[j];
                    if (ika.key !== iks.key) continue;
                    zs._activeKeyPool.splice(j, 1);
                    break;
                }
                zs._keyPool.splice(i, 1);
                i--;
            }
        }
    }

    /**
     * 设备连接断开时的处理函数
     * @param e 设备源类型
     */
    private static disconnectedHandle(e: DeviceSourceType): void {
        switch (e.deviceType) {
            case DeviceType.Keyboard:
                if (zs._device.keyboard) {
                    zs.subtractHandle(zs._device.keyboard!.deviceType);
                    zs.endCheck(zs._device.keyboard);
                    zs._device.keyboard = undefined;
                }
                break;
            case DeviceType.Mouse:
                if (zs._device.mouse) {
                    zs.subtractHandle(zs._device.mouse!.deviceType);
                    zs.endCheck(zs._device.mouse);
                    zs._device.mouse = undefined;
                }
                break;
            case DeviceType.Touch:
                if (zs._device.touch) {
                    zs.subtractHandle(zs._device.touch!.deviceType);
                    zs.endCheck(zs._device.touch);
                    zs._device.touch = undefined;
                }
                break;
            case DeviceType.Xbox:
                if (zs._device.xbox) {
                    zs.subtractHandle(zs._device.xbox!.deviceType);
                    zs.endCheck(zs._device.xbox);
                    zs._device.xbox = undefined;
                }
                break;
            case DeviceType.Switch:
                if (zs._device.switch) {
                    zs.subtractHandle(zs._device.switch!.deviceType);
                    zs.endCheck(zs._device.switch);
                    zs._device.switch = undefined;
                }
                break;
            case DeviceType.DualSense:
                if (zs._device.dualScene) {
                    zs.subtractHandle(zs._device.dualScene!.deviceType);
                    zs.endCheck(zs._device.dualScene);
                    zs._device.dualScene = undefined;
                }
                break;
            case DeviceType.DualShock:
                if (zs._device.dualShock) {
                    zs.subtractHandle(zs._device.dualShock!.deviceType);
                    zs.endCheck(zs._device.dualShock);
                    zs._device.dualShock = undefined;
                }
                break;
            case DeviceType.Generic:
                if (zs._device.generic) {
                    zs.subtractHandle(zs._device.generic!.deviceType);
                    zs.endCheck(zs._device.generic);
                    zs._device.generic = undefined;
                }
                break;
        }
    }

    /**
     * 开始活动输入键对象的输入检测
     */
    private static startCheck(): void {
        if (zs._req.id !== undefined) return;
        const itv = zs._options.checkInterval!;
        AnimationFrame.request(() => {
            zs._activeKeyPool.forEach(ks => ks.check());
        }, itv, itv + 200, zs._req);
    }

    /**
     * 结束特定设备相关的输入键对象的输入检测
     * @param device 设备
     */
    private static endCheck(device?: DeviceSource<DeviceType>): void {
        if (!device) {
            zs._activeKeyPool.length = 0;
        } else {
            for (let i = 0; i < zs._activeKeyPool.length; i++) {
                if (zs._activeKeyPool[i].key === DeviceInputKey.pointer) continue;
                if ((zs._activeKeyPool[i] as InputKeyStatus<DeviceType>).device.deviceType !== device.deviceType) continue;
                zs._activeKeyPool.splice(i, 1);
                i--;
            }
        }
        if (zs._activeKeyPool.length) return;
        AnimationFrame.cancel(zs._req);
    }

}
const zs = DeviceInputPreset;


export { DeviceInputPreset };
export type { IDeviceInputPresetOptions, IDeviceInputHandle };